'''
Created on Jul 1, 2009

@author: sternron
'''
from cliques.kPotentialClique import kPotentialClique
 
class potentialCliques(object):
    '''
    Holds all the potential cliques needed for a state
    Allows simulated node expansion
    THIS OBJECT IS DESIGNED TO BE SEMI-IMMUTABLE (INSERT FIRST, THEN NOT 
    '''
    instance_counter = 0
    
    def __init__(self,k,pcdb):
        '''
        Constructor
        '''
#        potentialCliques.instance_counter=potentialCliques.instance_counter+1
#        if potentialCliques.instance_counter % 100000==0:
#            print "counter=%d" % potentialCliques.instance_counter        
#        self.__expanded_to_kPc = dict()
#        self.__generated_to_kPc = dict()
        self.k = k      
        self.__generated = set()
        self.pcdb = []
        self.max_core = self.__insert_all(pcdb)            
        self.h = k-1-self.max_core
        self.hash_value=19*self.max_core
        self.terminal=(len(self.__generated)==0)
    
    def __insert_all(self,pcdb):
        ''' Insert a new kPc to self '''
        max_core=0
        for kPc in pcdb:
            self.pcdb.append(kPc)
            self.__generated.update(kPc.gcn) 
            
            core_len=len(kPc.core)
            if max_core<core_len:
                max_core=core_len
        
        return max_core
    
    def __iter__(self):
        return iter(self.pcdb)
    
    def find_gcn_members(self):
        '''
        Returns a list of all the nodes that are members of a gcn 
        '''
        return list(self.__generated)
    
    def has_generated(self):
        return not self.terminal
    
    def expand(self,node,new_neighbors):
        '''
           Return a new clique potentials that extends self by
           connecting node to neighbors
        ''' 
        new_pcdb = []
        k=self.k     
#        append_pcdb = new_pcdb.append
        
        for kPc in self.pcdb:  
            if node in kPc.gcn:                  
                # Check if without node kPc is still a potential
                new_gcn = list(kPc.gcn)
                new_gcn.remove(node)
                
                # Check if with node kPc can be extended
                extended_gcn = [gcn_member for gcn_member in new_gcn if gcn_member in new_neighbors]

                core_len = len(kPc.core)
                new_gcn_len = len(new_gcn)
                extended_gcn_len = len(extended_gcn)
                
                # If kPc can be retained without extension
                if (new_gcn_len>extended_gcn_len) and (new_gcn_len+core_len>=k):
                    updated_kPc = kPotentialClique(kPc.core, new_gcn)
                    new_pcdb.append(updated_kPc)
#                    append_pcdb(updated_kPc)
                    
                # If kPc can be extended
                if extended_gcn_len+core_len+1>=k:
                    extended_kPc = kPotentialClique(kPc.core+[node], extended_gcn)
                    new_pcdb.append(extended_kPc)
#                    append_pcdb(extended_kPc)
            else: # Retain the kPc for possible future expansions
                new_pcdb.append(kPc)
                 
        if len(new_neighbors)>=self.k-1:
            new_pcdb.append(kPotentialClique([node],new_neighbors))
#            append_pcdb(kPotentialClique([node],new_neighbors))
        
        return potentialCliques(self.k,new_pcdb)

    def same(self, other_potentials):
        ''' Check if oher contains the same kPc's '''
        if self.max_core!=other_potentials.max_core:
            return False
        if len(self.pcdb)!=len(other_potentials.pcdb):
            return False
        other_pcdb = list(other_potentials.pcdb)
        for kPc in self.pcdb:
            same_kPc=None
            for other_kPc in other_pcdb:
                if kPc.same(other_kPc)==True:
                    same_kPc=other_kPc
                    break
            if same_kPc is None:
                return False
            other_pcdb.remove(same_kPc)
        return True
        
            
            
            
            
            
            